<template>
  <div class="gis-map-component-public">
    <div
      :id="`map-component-${uuid}`"
      :ref="`mapBoxPublic-${uuid}`"
      class="map-component-class"
      @dragover="$event.preventDefault()"
    ></div>
    <!-- 图例 -->
    <gis-map-legend
      v-show="legendVisible && interactiveMode"
      :visible="mapLegend"
      :map-box-instance="mapBox"
      :parent-container="'gis-map-component-public'"
      :draggable="false"
      :overall-folding="false"
      :base-id="uuid"
    />
    <div v-if="mapLoading" class="layer-loading">
      <a-spin :delay="500" tip="Loading..."></a-spin>
    </div>
  </div>
</template>

<script lang="ts">
/**
 * gis 地图画布
 */
import { Component, Mixins, Prop, Watch } from 'vue-property-decorator'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
// @ts-ignore
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
import elementResizeDetectorMaker from 'element-resize-detector'
import { getMapTilesByMapTypeAndStyle } from '@/components/gis/map-package/map-config'
import { debounce } from 'lodash'
import GisMixin from '@/components/gis/public/gis-mixin'
import mapUtils from '@/components/gis/map-package/map-utils'
import {
  plotLayerByGeoJsonApi,
  plotLayerBySliceGeoJsonApi,
  queryLayersByTabIdApi,
  queryTabInfoByIdApi,
} from '@/api/gis'
import GisMapLegend from '@/components/gis/gis-analysis/analysis-drag-modal/GisMapLegend.vue'

export interface TabConfigData {
  layerType: number
  legend: boolean
  lnglat: boolean
  mapType: number
  scale: boolean
}

export function getUuid(length: number = 36) {
  const withLine = true // 带不带横线
  const uuidLength = length // 长度为36
  let radix = 16 // 16进制
  const chars: any[] = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(
    ''
  )
  const uuid: any[] = []
  let i: number
  radix = radix || chars.length
  /* eslint-disable */
  if (withLine) {
    let r: any
    uuid[8] = '-'
    uuid[13] = '-'
    uuid[18] = '-'
    uuid[23] = '-'
    uuid[14] = '4'
    for (i = 0; i < uuidLength; i += 1) {
      if (!uuid[i]) {
        r = 0 | (Math.random() * 16)
        uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]
      }
    }
  } else {
    for (i = 0; i < uuidLength; i += 1) {
      uuid[i] = chars[0 | (Math.random() * radix)]
    }
  }
  /* eslint-enable */
  return uuid.join('')
}

@Component({
  components: {
    GisMapLegend,
  },
})
export default class GisMapPublic extends Mixins(GisMixin) {
  @Prop({ type: Number }) id!: number
  @Prop({ type: Number }) projectId!: number
  @Prop({ type: Boolean, default: true }) interactiveMode!: boolean // 模式： true 可交互模式 / false 仅展示
  @Prop({ type: Boolean, default: true }) legendVisible!: boolean // (interactiveMode: true 生效) v-show 图例显示隐藏（例如尺寸过小， 隐藏图例）
  @Prop({ type: Boolean, default: false }) forbiddenLegend!: boolean // (interactiveMode: true 生效) 禁用图例
  private mapLoading: boolean = false
  public mapLegend: boolean = false // 图例是否显示
  public uuid: any = null
  public publishNo: string | null = null

  @Watch('id', { immediate: true })
  private watchId(id: number) {
    this.publishNo = this.$route.params.token
    if (id && this.projectId) {
      this.uuid = getUuid()
      this.$nextTick(() => {
        if (document.querySelector(`#map-component-${this.uuid}`)) {
          this.queryTabsByProjectId()
        }
      })
    }
  }

  /**
   * 获取地图配置 TODO 待优化 (暂时获取该项目下所有画布， 从中提取当前画布配置)
   * @private
   */
  private async queryTabsByProjectId() {
    let currentTabSetting: TabConfigData = {
      layerType: 1,
      legend: false,
      lnglat: true,
      mapType: 1,
      scale: true,
    }
    const response: any = await queryTabInfoByIdApi({
      data: {
        projectId: this.projectId,
        id: this.id,
        publishNo: this.publishNo,
      },
    })
    if (response.data.code === 100) {
      const setting: any = response.data.result.data
      currentTabSetting = {
        ...currentTabSetting,
        ...setting,
      }
      await this.initMap(currentTabSetting)
      await this.queryLayersByTabId()
    }
  }

  /**
   * 获取 tabId 下的所有图层
   * @private
   */
  private async queryLayersByTabId() {
    if (this.projectId && this.id) {
      this.mapLoading = true
      const response: any = await queryLayersByTabIdApi({
        data: {
          projectId: this.projectId,
          tabId: this.id,
          publishNo: this.publishNo,
        },
      })
      if (response.data.code === 100) {
        this.layerList = response.data.result
        if (this.layerList.length > 0) {
          this.queryLayerDataAndRender()
        } else {
          this.mapLoading = false
        }
      } else {
        this.mapLoading = false
      }
    }
  }

  /**
   * 画布尺寸调整
   */
  public mapResize() {
    const rootErd = elementResizeDetectorMaker()
    const mapBoxPublic = this.$refs[`mapBoxPublic-${this.uuid}`] as HTMLElement
    rootErd.listenTo(mapBoxPublic, this.mapBoxResizeDebounce)
    this.$once('hook:beforeDestroy', () => {
      rootErd.removeAllListeners(mapBoxPublic)
    })
  }
  // 防抖
  public mapBoxResizeDebounce = debounce(() => this._mapBoxResize(), 500)
  private _mapBoxResize() {
    this.mapBox.resize()
  }

  /**
   * 实例化地图
   */
  public async initMap(currentTabSetting: any) {
    this.mapBox = new mapboxgl.Map({
      container: `map-component-${this.uuid}`, // container ID
      style: getMapTilesByMapTypeAndStyle(
        currentTabSetting.mapType,
        currentTabSetting.layerType
      ),
      bounds: [
        [73.536163, 53.558094],
        [135.084259, 7.397329],
      ],
      center: [106.1157366, 37.95462],
      zoom: 4, // starting zoom
      maxZoom: 18,
      minZoom: 1.5,
      dragPan: true,
      // antialias: true,  // 抗锯齿
      attributionControl: true,
      renderWorldCopies: true, // 地图缩小时将渲染多个全局地图的副本
    })

    this.mapBox.on('load', () => {
      if (this.interactiveMode) {
        this.addControl(currentTabSetting)
        this.initAndUpdateMapboxOnMouseEvent()
      }
      this.mapResize()
    })
    this.mapBox.on('click', () => {})
  }

  /**
   * 添加控件
   */
  public addControl(currentTabSetting: any) {
    const { legend, lnglat, scale } = currentTabSetting
    if (lnglat) {
      this.addCoordinatesControl()
    }
    if (scale) {
      this.addMapScaleControl()
    }
    if (legend) {
      if (this.forbiddenLegend) {
        return
      }
      this.mapLegend = true
    }
  }

  /**
   * 获取图层数据并绘制
   */
  public async queryLayerDataAndRender() {
    const orderLayerList: any[] = this.layerList.sort((a: any, b: any) => {
      return a.order - b.order
    })
    this.initAddEmpty()
    this.sliceQueryLayersData(orderLayerList)
  }

  /**
   * 获取所有图层数据
   * @param layers
   */
  public async getLayerDataByLayers(layers: any[]) {
    const requests = layers
      .filter((layer: any) => layer.visible === 1)
      .map(async (layer: any) => {
        return this.queryLayerDataByLayerId(layer.id)
      })
    const allResponse: any = await Promise.allSettled(requests)
    allResponse.forEach((result: any) => {
      if (result.status === 'fulfilled') {
        const { id, response } = result.value
        if (response.data.code === 100) {
          this.layerData[id] = response.data.result
        }
      }
    })
  }

  public queryLayers: any[] = [] // 需要分片加载的图层
  public currentQueryGeojsonLayer: any = null // 当前分片获取 geojson 数据的图层
  public queryLayerDataIndex: number = 0 // 当前分片请求的图层索引

  /**
   * 分片获取所有地图图层数据 geojson
   */
  public sliceQueryLayersData(layers: any[]) {
    this.queryLayers = layers
    this.queryLayerDataIndex = 0
    this.currentQueryGeojsonLayer = this.layerList[this.queryLayerDataIndex]
    this.sliceQueryLayerData()
  }

  /**
   * 分片获取单个地图图层数据 geojson 第一片数据起步
   */
  public async sliceQueryLayerData() {
    const { currentQueryGeojsonLayer } = this
    if (currentQueryGeojsonLayer.visible === 1) {
      const data: any = await this.queryLayerDataBySliceGeoJson(
        currentQueryGeojsonLayer
      )
      if (data) {
        const { id } = currentQueryGeojsonLayer
        if (this.layerData[id]) {
          // @ts-ignore
          this.layerData[id].geojson.features = this.layerData[
            id
          ]?.geojson.features.concat(data.geojson.features)
        } else {
          this.layerData[currentQueryGeojsonLayer.id] = data
        }

        this.renderLayerByLayerIdAndData(this.currentQueryGeojsonLayer) // 渲染图层
      } else {
        this.$message.warning('图层加载失败！')
      }
      this.nextLayerSliceGeojsonData() // 下一个图层
    } else {
      this.renderLayerByLayerIdAndData(currentQueryGeojsonLayer)
      this.nextLayerSliceGeojsonData() // 下一个图层
    }
  }

  /**
   * 串行渲染图层
   */
  public renderLayerByLayerIdAndData(layer: any) {
    // 添加数据
    this.addOrUpdateSourceToMap(layer.id)
    // 检查是否有 label 图层
    const labelAttributeTag: any = layer.data?.tagInfo?.attributeTag?.find(
      (item: any) => item.tagType === 'label'
    )
    if (labelAttributeTag) {
      mapUtils.addSymbolLabelLayerByConfig(
        this.mapBox,
        layer,
        labelAttributeTag.config,
        this.currentRenderLayerId
      )
      this.currentRenderLayerId = `label-${layer.id}`
    }
    this.addLayerToMap(layer, this.currentRenderLayerId)
    this.currentRenderLayerId = String(layer.id)
  }

  /**
   * 获取下一个图层分片数据
   */
  public nextLayerSliceGeojsonData() {
    this.queryLayerDataIndex += 1
    if (this.queryLayers[this.queryLayerDataIndex]) {
      this.currentQueryGeojsonLayer = this.queryLayers[this.queryLayerDataIndex]
      this.sliceQueryLayerData()
      // 没有下一个
    } else {
      mapUtils.gisMapResetByVisibleLayers(
        this.mapBox,
        this.queryLayers,
        this.layerData
      )
      this.mapLoading = false
    }
  }

  /**
   * 分片获取图层数据接口
   */
  public async queryLayerDataBySliceGeoJson(layer: any) {
    let data: any = null // 数据
    let sliceJsonInfo: any = [] // 分片信息
    let sliceInfo: any = {} // 当前分片信息
    const { publishNo, projectId } = this
    async function sliceQueryGeojson() {
      if (sliceJsonInfo.length > 0) {
        sliceInfo = {
          sliceIndex: sliceJsonInfo[0].index,
          sliceOffset: sliceJsonInfo[0].offset,
          slicePageSize: sliceJsonInfo[0].pageSize,
        }
      }
      const parameters: any = {
        projectId, // 画布
        layerId: layer.id, // 图层id
        sliceSize: 1024 * 1024 * 4, //	每个数据分片的预估大小(仅第一次请求）
        sliceIndex: 0, //	数据分片索引，即数据的第几个分片
        sliceOffset: 0, //	数据分片偏移
        slicePageSize: 0, //	数据分片长度
        publishNo,
        ...sliceInfo,
      }
      const response: any = await plotLayerBySliceGeoJsonApi({
        data: parameters,
      })
      if (response.data.code === 100) {
        const { result } = response.data
        if (result.geojson) {
          // 处理获取到的图层分片数据
          // 存储
          if (data) {
            // @ts-ignore
            data.geojson.features = data.geojson.features.concat(
              result.geojson.features
            )
          } else {
            data = result
          }
        }
        // 代表是第一次请求
        if (result.slicejson) {
          sliceJsonInfo = result.slicejson.slice(1) // 去除第一个已经获取到的数据的分片信息
          if (sliceJsonInfo.length > 0) {
            await sliceQueryGeojson()
          }
        } else {
          sliceJsonInfo.shift() // 删除一个分片信息
          if (sliceJsonInfo.length > 0) {
            await sliceQueryGeojson()
          }
        }
      }
    }
    try {
      await sliceQueryGeojson()
      return data
    } catch (error) {
      console.error(error)
      return null
    }
  }

  /**
   * 获取图层数据 by layerId
   */
  public async queryLayerDataByLayerId(layerId: number) {
    const parameters = {
      projectId: this.projectId,
      layerId,
      publishNo: this.publishNo,
    }
    const response: any = await plotLayerByGeoJsonApi({ data: parameters })
    return {
      id: layerId,
      response,
    }
  }

  public currentRenderLayerId: string = 'base-empty-layer'
  /**
   * 渲染图层
   * @param layers
   * @private
   */
  private renderLayer(layers: any[]) {
    this.initAddEmpty()
    layers.forEach((layer: any) => {
      if (!this.layerData[layer.id]) {
        return
      }
      this.addOrUpdateSourceToMap(layer.id)
      // 检查是否有 label 图层
      const labelAttributeTag: any = layer.data?.tagInfo?.attributeTag?.find(
        (item: any) => item.tagType === 'label'
      )
      if (labelAttributeTag) {
        mapUtils.addSymbolLabelLayerByConfig(
          this.mapBox,
          layer,
          labelAttributeTag.config,
          this.currentRenderLayerId
        )
        this.currentRenderLayerId = `label-${layer.id}`
      }
      this.addLayerToMap(layer, this.currentRenderLayerId)
      this.currentRenderLayerId = String(layer.id)
    })
    mapUtils.gisMapResetByVisibleLayers(this.mapBox, layers, this.layerData) // 基于当前多个图层包围盒复位
  }

  /**
   * 添加数据源
   * @param id
   */
  public addOrUpdateSourceToMap(id: number) {
    const layerDataInfo: any = this.layerData[id] || { geojson: null }
    const sourceId: string = `source-${id}`
    const source: any = {
      type: 'geojson',
      data: layerDataInfo.geojson,
    }
    if (!this.mapBox.getSource(sourceId)) {
      this.mapBox.addSource(sourceId, source)
    } else {
      this.mapBox.getSource(sourceId).setData(source.data)
    }
  }

  /**
   * 添加图层到地图
   */
  public addLayerToMap(layer: any, beforeId: string = 'base-empty-layer') {
    const { id, type } = layer
    if (type === 'circle') {
      this.addPoint(layer, beforeId)
    } else if (type === 'line') {
      this.addLine(layer, beforeId)
    } else if (type === 'fill') {
      this.addPolygon(layer, beforeId)
    }
    this.addMouseEventToMapBoxByLayerId(String(id))
  }

  /**
   * 地图缩放
   * @param type
   */
  public mapZoom({ type }: { type: string }) {
    if (type === 'zoomIn') {
      this.mapBox.zoomIn()
    } else {
      this.mapBox.zoomOut()
    }
  }
}
</script>

<style scoped lang="less">
@import '@/components/gis/styles/scale-control.less';

.gis-map-component-public {
  height: 100%;
  position: relative;

  .map-component-class {
    height: 100%;
    position: relative;
    width: 100%;
  }

  .layer-loading {
    align-items: center;
    background-color: rgba(87, 96, 230, 0.1);
    display: flex;
    height: 100%;
    justify-content: center;
    left: 0;
    position: absolute;
    top: 0;
    width: 100%;
    z-index: 100;
  }
}
</style>
